package com.youaishujuhui.devops.web.service;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.WebSocketSession;

import com.alibaba.druid.util.StringUtils;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.youaishujuhui.devops.web.po.SSHExecResult;
import com.youaishujuhui.devops.web.util.Common;
import com.youaishujuhui.devops.web.util.WsUtil;
import com.youaishujuhui.devops.web.util.host.HostInstallTransferListener;

import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.connection.channel.direct.Session.Command;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
import net.schmizz.sshj.xfer.FileSystemFile;

@Service
public class SSHService {
	
	private static final int MAX_SIZE = 10;
	private static final int CACHE_MINUTES = 10;

	private LoadingCache<String, SSHClient> sshClientCache;
	
	@Autowired
	private PropertiesService propertiesService;
	
	private static final DefaultConfig config = new DefaultConfig();
	
	public SSHService() {
		
		RemovalListener<String, SSHClient> removalListener = new RemovalListener<String, SSHClient>() {
			@Override
			public void onRemoval(RemovalNotification<String, SSHClient> notification) {
				SSHClient sshClient = notification.getValue();
			    closeSSHClient(sshClient);
			}
		};
		
		sshClientCache = CacheBuilder.newBuilder()
	       .maximumSize(MAX_SIZE)
	       .expireAfterAccess(CACHE_MINUTES, TimeUnit.MINUTES)
	       .removalListener(removalListener)
	       .build(
    		   new CacheLoader<String, SSHClient>() {

    		   @Override
			   public SSHClient load(String key) throws Exception {
    			   String ip = key.split("_")[0];
    			   int sshPort = Integer.parseInt(key.split("_")[1]);
				   return makeSSHClientWithPrivateKey(ip, sshPort);
			   }
	             
	       });
	}
	
	public SSHExecResult exec(String destIp, int sshPort, String user, String passwd, String cmd) throws IOException {
		final SSHClient ssh = new SSHClient(config);
		
		ssh.addHostKeyVerifier(new PromiscuousVerifier());
    	
    	ssh.connect(destIp, sshPort);
        
        Session session = null;
        
        SSHExecResult result = new SSHExecResult();
        
        try {
            ssh.authPassword(user, passwd);
            
            session = ssh.startSession();
            final Command command = session.exec(cmd);

            // 等待命令执行完毕
            command.join();
            
            result.setStdout(IOUtils.readFully(command.getInputStream()).toString());
            result.setStderr(IOUtils.readFully(command.getErrorStream()).toString());
            result.setExitStatus(command.getExitStatus());
            
            return result;
        } finally {
        	closeSession(session);
            closeSSHClient(ssh);
        }
	}
	
	public SSHExecResult exec(String destIp, int sshPort, String cmd) throws IOException {
		
		final SSHClient ssh = new SSHClient(config);
		
        ssh.addHostKeyVerifier(new PromiscuousVerifier());
        
        ssh.connect(destIp, sshPort);

        KeyProvider keys = ssh.loadKeys(propertiesService.getSshKeyPath(), propertiesService.getSshKeyPasswd());
        
        Session session = null;
        
        SSHExecResult result = new SSHExecResult();
        
        try {
            ssh.authPublickey(propertiesService.getSshUser(), keys);
            
            session = ssh.startSession();
            final Command command = session.exec(cmd);

            // 等待命令执行完毕
            command.join();
            
            result.setStdout(IOUtils.readFully(command.getInputStream()).toString());
            result.setStderr(IOUtils.readFully(command.getErrorStream()).toString());
            result.setExitStatus(command.getExitStatus());
            
            return result;
        } finally {
        	closeSession(session);
            closeSSHClient(ssh);
        }
	}
	
	public SSHExecResult execUseCache(String destIp, int sshPort, String cmd) throws IOException, ExecutionException {
		SSHClient ssh = null;

        Session session = null;
        
        SSHExecResult result = new SSHExecResult();
        
        try {
        	ssh = sshClientCache.get(destIp + "_" + sshPort);
            session = ssh.startSession();
            final Command command = session.exec(cmd);

            // 等待命令执行完毕
            command.join();
            
            result.setStdout(IOUtils.readFully(command.getInputStream()).toString());
            result.setStderr(IOUtils.readFully(command.getErrorStream()).toString());
            result.setExitStatus(command.getExitStatus());
            
            return result;
        } finally {
        	closeSession(session);
        }
	}
	
	public void SFTPPut(String destIp, int sshPort, String fromFile, String toFile) throws IOException {
		final SSHClient ssh = new SSHClient(config);
		SFTPClient sftp = null;

        try {
        	ssh.addHostKeyVerifier(new PromiscuousVerifier());
        	
        	ssh.connect(destIp, sshPort);

            KeyProvider keys = ssh.loadKeys(propertiesService.getSshKeyPath(), propertiesService.getSshKeyPasswd());
            
            ssh.authPublickey(propertiesService.getSshUser(), keys);
            
            sftp = ssh.newSFTPClient();
            
            sftp.put(new FileSystemFile(fromFile), toFile);

        } finally {
			closeFtpClient(sftp);
			closeSSHClient(ssh);
        }
	}
	
	public void SFTPPut(String destIp, int sshPort, String user, String passwd,
			String fromFile, String toFile, HostInstallTransferListener listener) throws IOException {
		final SSHClient ssh = new SSHClient(config);
		SFTPClient sftp = null;

        try {        	
        	ssh.addHostKeyVerifier(new PromiscuousVerifier());
        	
        	ssh.connect(destIp, sshPort);
            
            ssh.authPassword(user, passwd);
            
            sftp = ssh.newSFTPClient();
            
            sftp.getFileTransfer().setTransferListener(listener);

            sftp.put(new FileSystemFile(fromFile), toFile);
        } finally {
        	closeFtpClient(sftp);
			closeSSHClient(ssh);
        }
	}

	public void SFTPGet(String destIp, int sshPort, String fromFile, String toFile) throws IOException {
		final SSHClient ssh = new SSHClient(config);
		SFTPClient sftp = null;

        try {
        	ssh.addHostKeyVerifier(new PromiscuousVerifier());
        	
        	ssh.connect(destIp, sshPort);

        	KeyProvider keys = ssh.loadKeys(propertiesService.getSshKeyPath(), propertiesService.getSshKeyPasswd());
            
            ssh.authPublickey(propertiesService.getSshUser(), keys);
            
            sftp = ssh.newSFTPClient();
            
            sftp.get(fromFile, new FileSystemFile(toFile));
        } finally {
        	closeFtpClient(sftp);
			closeSSHClient(ssh);
        }
	}

	public void exec(String destIp, Integer sshPort, String user, String passwd, String cmd,
			WebSocketSession wsSession) throws IOException {
		
		final SSHClient ssh = new SSHClient(config);
		
		ssh.addHostKeyVerifier(new PromiscuousVerifier());
    	
    	ssh.connect(destIp, sshPort);
        
        Session session = null;
                
        try {
            ssh.authPassword(user, passwd);
            
            session = ssh.startSession();
            final Command command = session.exec(cmd);
            
            do {
            	String tmp = read(command.getInputStream());
            	if (!StringUtils.isEmpty(tmp)) {
            		tmp = tmp.replaceAll("\\n", "<br>")
    						.replaceAll("\\e\\[1;32m", "<font color=\"green\">")
    						.replaceAll("\\e\\[0m", "</font>")
    						.replaceAll("\\e\\[1;31m", "<font color=\"red\">");
    				//wsSession.sendMessage(new TextMessage(tmp.substring(0, tmp.length() - 4)));
    				WsUtil.send(wsSession, Common.HOST_INIT_DIV_ID_PREFIX + destIp, tmp.substring(0, tmp.length() - 4));
            	}
            	
            	try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					return;
				}

            } while (!command.isEOF()) ;
            
            String tmp = read(command.getInputStream());
        	if (!StringUtils.isEmpty(tmp)) {
        		tmp = tmp.replaceAll("\\n", "<br>")
						.replaceAll("\\e\\[1;32m", "<font color=\"red\">")
						.replaceAll("\\e\\[0m", "</font>")
						.replaceAll("\\e\\[1;31m", "<font color=\"green\">");
				//wsSession.sendMessage(new TextMessage(tmp.substring(0, tmp.length() - 4)));
				WsUtil.send(wsSession, Common.HOST_INIT_DIV_ID_PREFIX + destIp, tmp.substring(0, tmp.length() - 4));
        	}
        } finally {
        	closeSession(session);
			closeSSHClient(ssh);
        }
	}
	

	public void exec(String destIp, int sshPort, String cmd, WebSocketSession wsSession,
			String prefix) throws IOException {
		final SSHClient ssh = new SSHClient(config);
		
        ssh.addHostKeyVerifier(new PromiscuousVerifier());
        
        ssh.connect(destIp, sshPort);

        KeyProvider keys = ssh.loadKeys(propertiesService.getSshKeyPath(), propertiesService.getSshKeyPasswd());
        
        Session session = null;
        
        try {
            ssh.authPublickey(propertiesService.getSshUser(), keys);
            
            session = ssh.startSession();
            final Command command = session.exec(cmd);

            do {
            	String tmp = read(command.getInputStream());
            	if (!StringUtils.isEmpty(tmp)) {
            		tmp = tmp.replaceAll("\\n", "<br>");
    				WsUtil.send(wsSession, prefix + destIp, tmp.substring(0, tmp.length() - 4));
            	}
            	
            	try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					return;
				}

            } while (!command.isEOF()) ;
            
            String tmp = read(command.getInputStream());
        	if (!StringUtils.isEmpty(tmp)) {
        		tmp = tmp.replaceAll("\\n", "<br>");
				WsUtil.send(wsSession, prefix + destIp, tmp.substring(0, tmp.length() - 4));
        	}
        } finally {
        	closeSession(session);
            closeSSHClient(ssh);
        }
	}
	
	private SSHClient makeSSHClientWithPrivateKey(String ip, int sshPort) {
		final SSHClient ssh = new SSHClient(config);
        ssh.addHostKeyVerifier(new PromiscuousVerifier());

        try {
			ssh.connect(ip, sshPort);
	        KeyProvider keys = ssh.loadKeys(propertiesService.getSshKeyPath(), propertiesService.getSshKeyPasswd());
	        ssh.authPublickey(propertiesService.getSshUser(), keys);
        } catch (IOException e) {
			e.printStackTrace();
			return null;
		}

        return ssh;
	}
	
    private String read(InputStream inputStream) throws IOException {
    	final int bufferSize = inputStream.available();
    	final char[] buffer = new char[bufferSize];
    	final StringBuilder out = new StringBuilder();
    	Reader in = new InputStreamReader(inputStream, "UTF-8");
    	
    	int rsz = in.read(buffer, 0, buffer.length);
    	    
    	out.append(buffer, 0, rsz);
    	
    	return out.toString();
    }
	
	private void closeSession(Session session) {
		try {
            if (session != null) {
                session.close();
            }
        } catch (IOException e) {
        }
	}
	
	private void closeFtpClient(SFTPClient sftp) {
		if (sftp != null) {
			try {
				sftp.close();
			} catch (IOException e) {
			}
		}
	}
	
	private void closeSSHClient(SSHClient sshClient) {
		if (sshClient != null) {
			try {
				sshClient.disconnect();
			} catch (IOException e) {
			}
			
			try {
				sshClient.close();
			} catch (IOException e) {
			}
		}
	}
	
}
